/*
 * Copyright 2015 Google Inc. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.blockly.ohos.demo;

import com.google.blockly.ohos.BlocklySectionsAbility;
import com.google.blockly.ohos.ZoomBehavior;
import com.google.blockly.ohos.codegen.CodeGenerationRequest;
import com.google.blockly.ohos.codegen.LoggingCodeGeneratorCallback;
import com.google.blockly.ohos.control.BlocklyController;
import com.google.blockly.model.Block;
import com.google.blockly.model.DefaultBlocks;
import com.google.blockly.utils.LogUtils;
import ohos.aafwk.content.Intent;
import ohos.agp.colors.RgbColor;
import ohos.agp.components.*;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.utils.Color;
import ohos.agp.utils.LayoutAlignment;
import ohos.agp.window.dialog.PopupDialog;
import ohos.agp.window.dialog.ToastDialog;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;


/**
 * Ability with Developer oriented tests.
 */
public class DevTestsAbility extends BlocklySectionsAbility {
    private static final String TAG = "DevTestsAbility";

    public static final String SAVE_FILENAME = "dev_tests_workspace.xml";

    private static final List<String> BLOCK_DEFINITIONS = Collections.unmodifiableList(
            Arrays.asList(
                    DefaultBlocks.LIST_BLOCKS_PATH,
                    DefaultBlocks.LOGIC_BLOCKS_PATH,
                    DefaultBlocks.LOOP_BLOCKS_PATH,
                    DefaultBlocks.MATH_BLOCKS_PATH,
                    DefaultBlocks.TEXT_BLOCKS_PATH,
                    DefaultBlocks.VARIABLE_BLOCKS_PATH,
                    "default/test_blocks.json",
                    "sample_sections/mock_block_definitions.json"
            ));

    private static final int CARPET_SIZE = 1000;

    public static final String WORKSPACE_FOLDER_PREFIX = "sample_sections/level_";

    private static final int SECTION_NUM = 3;
    private String[] mSectionNames;

    private Text mTitle;

    protected Checkbox mScrollableMenuItem;
    protected Checkbox mPinchZoomMenuItem;
    protected Checkbox mLogEventsMenuItem;

    protected CodeGenerationRequest.CodeGeneratorCallback mCodeGeneratorCallback =
            new LoggingCodeGeneratorCallback(this, TAG);
    protected LogAllEventsCallback mEventsCallback = new LogAllEventsCallback("BlocklyEvents");

    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        mTitle = (Text) findComponentById(com.google.blockly.ohos.ResourceTable.Id_title);
        mTitle.setText(mSectionNames[0]);
        DependentLayout actionRoot = (DependentLayout) findComponentById(ResourceTable.Id_action_root);
        ShapeElement shapeElement = new ShapeElement();
        shapeElement.setRgbColor(RgbColor.fromArgbInt(Color.BLACK.getValue()));
        actionRoot.setBackground(shapeElement);
    }

    @Override
    public void onClick(Component component) {
        super.onClick(component);
        onItemClicked(component.getId());
    }

    private void onItemClicked(int id) {
        if (id == ResourceTable.Id_action_airstrike) {
            airstrike();
        } else if (id == ResourceTable.Id_action_carpet_bomb) {
            carpetBomb();
        } else if (id == ResourceTable.Id_action_spaghetti) {
            loadSpaghetti();
        } else if (id == ResourceTable.Id_action_create_variable) {
            getController().requestAddVariable("item");
        } else {
            return;
        }
        mMenuDialog.hide();
    }

    public void showMenuComponent() {
        if (mMenuDialog == null) {
            Component menu = LayoutScatter.getInstance(getContext()).parse(getDefaultMenuLayoutRes(), null, false);
            ZoomBehavior zb = getController().getWorkspaceHelper().getZoomBehavior();
            mScrollableMenuItem = (Checkbox) menu.findComponentById(ResourceTable.Id_scrollable_menuitem);
            mScrollableMenuItem.setEnabled(false); // TODO: Dynamic Zoom Behavior
            mScrollableMenuItem.setChecked(zb.isScrollEnabled());
            mPinchZoomMenuItem = (Checkbox) menu.findComponentById(ResourceTable.Id_pinch_zoom_menuitem);
            mPinchZoomMenuItem.setEnabled(false); // TODO: Dynamic Zoom Behavior
            mPinchZoomMenuItem.setChecked(zb.isPinchZoomEnabled());
            mLogEventsMenuItem = (Checkbox) menu.findComponentById(ResourceTable.Id_log_events_menuitem);
            mLogEventsMenuItem.setCheckedStateChangedListener((absButton, b) -> {
                setLogEvents(b);
                mMenuDialog.hide();
            });
            menu.findComponentById(ResourceTable.Id_action_clear).setClickedListener(this);
            menu.findComponentById(ResourceTable.Id_action_run).setClickedListener(this);
            menu.findComponentById(ResourceTable.Id_action_airstrike).setClickedListener(this);
            menu.findComponentById(ResourceTable.Id_action_carpet_bomb).setClickedListener(this);
            menu.findComponentById(ResourceTable.Id_action_spaghetti).setClickedListener(this);
            menu.findComponentById(ResourceTable.Id_action_create_variable).setClickedListener(this);
            mMenuDialog = new PopupDialog(getContext(), findComponentById(com.google.blockly.ohos.ResourceTable.Id_action_more));
            mMenuDialog.setCustomComponent(menu);
            mMenuDialog.setDialogListener(() -> {
                mMenuDialog.hide();
                return false;
            });
        }
        if (!mMenuDialog.isShowing()) {
            mMenuDialog.show();
        }
    }

    public int getDefaultMenuLayoutRes() {
        return ResourceTable.Layout_dev_tests_menu;
    }

    /**
     * @param logEvents Enable event logging if true. Otherwise, disable.
     */
    private void setLogEvents(boolean logEvents) {
        if (logEvents) {
            getController().addCallback(mEventsCallback);
        } else {
            getController().removeCallback(mEventsCallback);
        }
        mLogEventsMenuItem.setChecked(logEvents);
    }

    /**
     * Place one instance of each of the toolbox's blocks, on the workspace, all in the same place.
     */
    private void airstrike() {
        List<Block> blocks = new ArrayList<>();
        getController().getWorkspace().getToolboxContents().getAllBlocksRecursive(blocks);
        for (int i = 0; i < blocks.size(); i++) {
            Block copiedModel = blocks.get(i).deepCopy();
            copiedModel.setPosition(0, 0);
            getController().addRootBlock(copiedModel);
        }
    }

    /**
     * Place one instance of each of the toolbox's blocks, randomly across a section of the
     * workspace.
     */
    private void carpetBomb() {
        List<Block> blocks = new ArrayList<>();
        getController().getWorkspace().getToolboxContents().getAllBlocksRecursive(blocks);
        for (int i = 0; i < blocks.size(); i++) {
            Block copiedModel = blocks.get(i).deepCopy();
            copiedModel.setPosition((int) (Math.random() * CARPET_SIZE) - CARPET_SIZE / 2,
                    (int) (Math.random() * CARPET_SIZE) - CARPET_SIZE / 2);
            getController().addRootBlock(copiedModel);
        }
    }

    /**
     * Loads a workspace with heavily nested blocks.
     */
    private void loadSpaghetti() {
        try {
            getController().loadWorkspaceContents(getResourceManager()
                    .getRawFileEntry("entry/resources/rawfile/sample_sections/workspace_spaghetti.xml")
                    .openRawFile());
        } catch (Exception e) {
            new ToastDialog(getApplicationContext())
                    .setText(getString(ResourceTable.String_toast_workspace_file_not_found))
                    .setAlignment(LayoutAlignment.CENTER)
                    .setDuration(2000)
                    .show();
            LogUtils.e(TAG, "Failed to load spaghetti workspace.");
        }
    }

    @Override
    protected String getToolboxContentsXmlPath() {
        // Expose a different set of blocks to the user at each level.
        return WORKSPACE_FOLDER_PREFIX + (getCurrentSectionIndex() + 1) + "/toolbox.xml";
    }

    @Override
    protected List<String> getBlockDefinitionsJsonPaths() {
        return BLOCK_DEFINITIONS;
    }

    @Override
    protected List<String> getGeneratorsJsPaths() {
        List<String> paths = new ArrayList<String>(1);
        paths.add("sample_sections/generators.js");
        return paths;
    }

    @Override
    protected void onLoadInitialWorkspace() {
        try {
            getController().loadWorkspaceContents(getResourceManager()
                    .getRawFileEntry("entry/resources/rawfile/sample_sections/mock_block_initial_workspace.xml")
                    .openRawFile());
        } catch (Exception e) {
            LogUtils.e(TAG, "Failed to load workspace");
        }
        addDefaultVariables();
    }

    @Override
    protected CodeGenerationRequest.CodeGeneratorCallback getCodeGenerationCallback() {
        // Uses the same callback for every generation call.
        return mCodeGeneratorCallback;
    }

    @Override
    protected BaseItemProvider onCreateSectionsListAdapter() {
        // Create the game levels with the labels "Level 1", "Level 2", etc., displaying
        // them as simple text items in the sections drawer.
        mSectionNames = new String[SECTION_NUM];
        for (int i = 0; i < SECTION_NUM; ++i) {
            mSectionNames[i] = "Section " + (i + 1);
        }
        return new Adapter();
    }

    private class Adapter extends BaseItemProvider {

        @Override
        public int getCount() {
            return mSectionNames.length;
        }

        @Override
        public Object getItem(int i) {
            return mSectionNames[i];
        }

        @Override
        public long getItemId(int i) {
            return i;
        }

        @Override
        public Component getComponent(int i, Component component, ComponentContainer componentContainer) {
            Adapter.ViewHolder viewHolder;
            if (component == null) {
                component = LayoutScatter.getInstance(getContext()).parse(ResourceTable.Layout_sections_item, null, false);
                viewHolder = new Adapter.ViewHolder();
                viewHolder.itemView = (Text) component.findComponentById(ResourceTable.Id_text_item);
                component.setTag(viewHolder);
            } else {
                viewHolder = (Adapter.ViewHolder) component.getTag();
            }
            viewHolder.itemView.setText(mSectionNames[i]);
            ShapeElement shapeElement = new ShapeElement();
            shapeElement.setRgbColor(RgbColor.fromArgbInt(
                    getCurrentSectionIndex() == i ? Color.CYAN.getValue() : Color.TRANSPARENT.getValue()));
            viewHolder.itemView.setBackground(shapeElement);
            return component;
        }

        private class ViewHolder {
            public Text itemView;
        }

    }

    @Override
    protected boolean onSectionChanged(int oldSection, int newSection) {
        mTitle.setText(mSectionNames[newSection]);
        reloadToolbox();
        return true;
    }

    @Override
    protected void onInitBlankWorkspace() {
        addDefaultVariables();
    }

    private void addDefaultVariables() {
        // TODO: (#22) Remove this override when variables are supported properly
        BlocklyController controller = getController();
        controller.addVariable("item");
        controller.addVariable("zim");
        controller.addVariable("gir");
        controller.addVariable("tak");
    }

    @Override
    protected void onAutosave() {
        // Dev tests doesn't autosave/restore the user's workspace by default as we load a specific
        // workspace in onLoadInitialWorkspace.
        return;
    }

    @Override
    protected boolean onAutoload() {
        // Dev tests doesn't autosave/restore the user's workspace by default as we load a specific
        // workspace in onLoadInitialWorkspace.
        return false;
    }

    @Override
    protected String getWorkspaceSavePath() {
        return SAVE_FILENAME;
    }
}
